/*
* Slave Loconet I/O handler
*
* Can be used by any I2C master (RasPi, BeagleBone...)
*
* Copyright (c) 2015 John Plocher, Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
*
* Based on i2c/arduino info found in
* http://playground.arduino.cc/Main/WireLibraryDetailedReference
* http://www.gammon.com.au/forum/?id=10896
* as well as an embedded Loconet-compatible arduino shield/interface such as
* http://www.spcoast.com/wiki/index.php/LocoShield
*
* This sketch is an interface between I2C and a LocoShield, allowing an external I2C master
* to send and receive LocoNet packets without having to deploy a timing-constrained real time
* hardware driver
*
* The master writes either a 1-byte command or a 2-16 byte loconet packet to the slave.
* If a command is sent, the master follows up with a read of the information asked for.
*
* Current status (2015-02-14): I2C interactions are solid, Rx path seems to work, Tx path untested
*/
#define DEBUG
#include <elapsedMillis.h>
#include <LocoNet.h>
#define LNET_TX_PIN 7
#define LNET_RX_PIN 8
elapsedMillis et;
#include <Wire.h>
// defines BUFFER_LENGTH (default on UNO = 32) which is max i2c transfer allowed...
// Since LNet max packet size (in LNPE) is 16, this should be no problem.
#define INT_PIN 12 // tie to master to communicate "something ready to read"
#define INTERRUPT_ON HIGH
#define INTERRUPT_OFF LOW
#define SLAVE_ADDRESS 0x01
/*
* Commands
*/
enum {
GETAVAILABLE = 0, // -> 1 byte, Packet Length to read next (0=no packet to read_
GETPACKET = 1, // -> variable (2-16), LNet packet with checksum
GETERRORS = 2, // -> 10 bytes (see LocoErrors struct below)
GETVERSION = 3, // -> 1 byte
NUMCOMMANDS
};
static byte protocol_version = 0x01;
LnBuf LnTxBuffer;
lnMsg *LnTxPacket;
lnMsg *LnRxPacket;
volatile struct LocoErrors {
int RxPackets; // 2 bytes each * 5 items = 10 bytes of data
int RxErrors;
int TxPackets;
int TxErrors;
int Collisions;
} errors;
volatile byte receivedCommand; // sent by master, used to determine how to respond to reads
void sendavailable2master(void) {
byte l = LocoNet.length();
Wire.write(l);
}
void sendpacket2master(void) {
if (LocoNet.length()) {
LnRxPacket = LocoNet.receive();
Wire.write((const uint8_t *)LnRxPacket->data, getLnMsgSize( LnRxPacket ));
LnRxPacket = NULL;
}
}
void senderrors2master(void) {
LnBufStats *stats = LocoNet.getStats(); // get and copy stats maintained by the Loconet subsystem
errors.RxPackets = stats->RxPackets;
errors.RxErrors = stats->RxErrors;
errors.TxPackets = stats->TxPackets;
errors.TxErrors = stats->TxErrors;
errors.Collisions = stats->Collisions;
Wire.write((const uint8_t *)&errors, sizeof (struct LocoErrors));
}
// callback from Wire - the master is reading from us, I2C conversation is in progress...
void requestEvent(){
digitalWrite(INT_PIN, INTERRUPT_OFF); // reset the INT, since someone is talking to us...
switch (receivedCommand) {
case GETAVAILABLE: sendavailable2master(); break;
case GETPACKET: sendpacket2master(); break;
case GETERRORS: senderrors2master(); break;
case GETVERSION: Wire.write(protocol_version); break;
}
}
// callback from Wire - the master is writing to us, I2C conversation is in progress...
void receiveEvent(int bytesReceived) {
digitalWrite(INT_PIN, INTERRUPT_OFF); // reset the INT, since someone is talking to us...
if (bytesReceived == 1) { // One byte means a command...
receivedCommand = Wire.read();
if (receivedCommand >= NUMCOMMANDS) receivedCommand = GETAVAILABLE;
} else { // More than one byte sent must be a packet to send
int validbytes = min(bytesReceived, BUFFER_LENGTH);
for (int a = 0; a < validbytes; a++) {
addByteLnBuf( &LnTxBuffer, Wire.read()); // grab at max 32 (on UNO...) bytes...
// side effect: when a whole, valid packet is in the tx buffer, the main loop() will send it
}
for (int a = validbytes; a < bytesReceived; a++) {
Wire.read(); // if we receive more data then allowed just throw it away
// though the stack is probably already corrupted in Wire()...
}
}
}
#if defined(DEBUG)
void printW6(int x) {
if (x < 10000) Serial.print(" ");
if (x < 1000) Serial.print(" ");
if (x < 100) Serial.print(" ");
if (x < 10) Serial.print(" ");
Serial.print(x, DEC);
Serial.print(" ");
}
void printW4(int x) {
if (x < 100) Serial.print(" ");
if (x < 10) Serial.print(" ");
Serial.print(x, DEC);
Serial.print(" ");
}
int was = -1; // for only-on-change debug printing in loop()
#endif
void setup() {
delay(10000);
#if defined(DEBUG)
Serial.begin(115200);
while (!Serial);
Serial.println("LocoNet I2C Slave");
et = 0;
Serial.println("LoconetRX wi ri rpi col len RXpkt RXerr TXpkt TXerr Colls");
#endif
pinMode(13, OUTPUT); // LED
pinMode(INT_PIN, OUTPUT); // interrupt pin
digitalWrite(INT_PIN, INTERRUPT_OFF);
pinMode(LNET_TX_PIN, OUTPUT); // Loconet Send Data (7)
pinMode(LNET_RX_PIN, INPUT); // Loconet RxD (8)
LocoNet.init(LNET_TX_PIN); // initialize the LocoNet interface
initLnBuf(&LnTxBuffer);
receivedCommand = GETAVAILABLE;
Wire.begin(SLAVE_ADDRESS);
Wire.onRequest(requestEvent);
Wire.onReceive(receiveEvent);
}
void loop() {
// wait for incoming packets, tell master via an interrupt line...
int a = LocoNet.available();
#if defined(DEBUG)
if ((a != was) || (et > 10000)) {
Serial.print( a ? "*Available* "
: " no traffic ");
printW4(LocoNet.LnBuffer.WriteIndex);
printW4(LocoNet.LnBuffer.ReadIndex);
printW4(LocoNet.LnBuffer.ReadPacketIndex);
printW4(LocoNet.LnBuffer.CheckSum);
printW4(LocoNet.LnBuffer.ReadExpLen);
LnBufStats *stats = LocoNet.getStats(); // get and copy stats maintained by the Loconet subsystem
printW6(stats->RxPackets);
printW6(stats->RxErrors);
printW6(stats->TxPackets);
printW6(stats->TxErrors);
printW6(stats->Collisions);
Serial.println();
et = 0;
was = a;
}
#endif
if ( a ) {
digitalWrite(INT_PIN, INTERRUPT_ON); // assert INT to master
digitalWrite(13, INTERRUPT_ON); // echo it on onboard LED for debugging
} else {
digitalWrite(INT_PIN, INTERRUPT_OFF); // nothing to see here...
digitalWrite(13, INTERRUPT_OFF); // echo it on onboard LED for debugging
}
LnTxPacket = recvLnMsg( &LnTxBuffer );
if (LnTxPacket) {
LocoNet.send( (lnMsg*)LnTxPacket );
}
}
|